package fileutils

import (
	"encoding/json"
	"errors"
	"log/slog"
	"sync"

	"github.com/gorilla/websocket"
	"gitlab.local/DO-module/new-filemanage-module/src/types"
)

type OnClose func(string)
type OnMessage func(b []byte)

// ThreadSafeWsConn 因为 websocket.Conn 并发写会 panic，这里加锁以支持并发安全
type ThreadSafeWsConn struct {
	*websocket.Conn
	l            sync.Mutex
	logger       *slog.Logger
	onCloseHooks []OnClose
	onMessage    OnMessage
	sessionId    string
}

func Wrap(conn *websocket.Conn, logger *slog.Logger, sessionId string) *ThreadSafeWsConn {
	return &ThreadSafeWsConn{
		Conn:      conn,
		logger:    logger,
		onMessage: func(b []byte) {},
		sessionId: sessionId,
	}
}

func (t *ThreadSafeWsConn) WriteMessage(messageType int, data []byte) error {
	t.l.Lock()
	defer t.l.Unlock()
	return t.Conn.WriteMessage(messageType, data)
}

func (t *ThreadSafeWsConn) SetOnMessage(onMessage OnMessage) {
	t.onMessage = onMessage
}

func (t *ThreadSafeWsConn) AddOnClose(onClose OnClose) {
	t.onCloseHooks = append(t.onCloseHooks, onClose)
}

func (t *ThreadSafeWsConn) SessionId() string {
	return t.sessionId
}

func (t *ThreadSafeWsConn) RunMessageReadLoop() {
	for {
		mt, b, err := t.ReadMessage()
		if err != nil {
			if websocket.IsCloseError(err, websocket.CloseGoingAway, websocket.CloseNormalClosure) {
				t.logger.Info("normal closed connection")
			} else {
				t.logger.Error("connection read error, closing...", "err", err)
			}
			for i := range t.onCloseHooks {
				t.onCloseHooks[i](t.sessionId)
			}
			t.Close()
			break
		}
		switch mt {
		case websocket.PingMessage:
			t.WriteMessage(websocket.PongMessage, nil)
		case websocket.BinaryMessage:
			fallthrough
		case websocket.TextMessage:
			t.onMessage(b)
		default:
			t.logger.Error(types.ErrUnsupportMessageType(mt).Err.Error(), "type", mt)
			t.WriteMessage(websocket.TextMessage, types.ErrUnsupportMessageType(mt).Marshal())
		}
	}
}

func (t *ThreadSafeWsConn) Publish(e types.Event) error {
	b, err := json.Marshal(e)
	if err != nil {
		return err
	}
	err = t.WriteMessage(websocket.TextMessage, b)
	if errors.Is(err, websocket.ErrCloseSent) {
		t.logger.Warn("send message error", "err", err)
		return nil
	}
	return err
}
